package com.jzo2o.market.service.impl;

import cn.hutool.db.DbRuntimeException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import com.jzo2o.redis.utils.RedisSyncQueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.jzo2o.common.constants.ErrorInfo.Code.SEIZE_COUPON_FAILD;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<String> seizeCouponScript;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private IActivityService activityService;

    @Resource
    private ICouponUseBackService couponUseBackService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;
    
    
    
    /**
     * 获取可用优惠券列表
     * @param totalAmount
     * @return
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {
        //当前用户id
        Long userId = UserContext.currentUserId();
        //拼装 sql 查询当前用户可用的优惠券
        List<Coupon> list = lambdaQuery()
                .eq(Coupon::getUserId, userId)//限定查询本用户
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())//优惠券未使用
                .gt(Coupon::getValidityTime, LocalDateTime.now())//优惠券还没有过期
                .le(Coupon::getAmountCondition, totalAmount)//满减金额小于等于订单金额
                .list();
        //如果优惠券列表为空则返回空列表
        if(CollUtils.isEmpty(list)){
            return Collections.emptyList();
        }

        //计算优惠金额
        List<AvailableCouponsResDTO> collect = list.stream().peek(coupon -> coupon.setDiscountAmount(CouponUtils.calDiscountAmount(coupon, totalAmount)))//计算优惠金额
                .filter(coupon -> coupon.getDiscountAmount().compareTo(new BigDecimal(0)) > 0 && coupon.getDiscountAmount().compareTo(totalAmount) < 0)
                .map(coupon -> BeanUtils.toBean(coupon, AvailableCouponsResDTO.class))
                .sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed())//按优惠金额降序
                .collect(Collectors.toList());

        //返回优惠券列表(包括了优惠金额)

        return collect;
    }


    /**
     * 使用优惠券
     * @param couponUseReqDTO
     */
    @Override
    public CouponUseResDTO use(CouponUseReqDTO couponUseReqDTO) {
        //判空
        if (ObjectUtils.isNull(couponUseReqDTO.getOrdersId()) ||
                ObjectUtils.isNull(couponUseReqDTO.getTotalAmount()))
        {
            throw new BadRequestException("优惠券核销的订单信息为空");
        }
        //用户id
        Long userId = UserContext.currentUserId();
        //根据优惠券id查询优惠券信息
        Coupon coupon = getById(couponUseReqDTO.getId());
        // 优惠券判空
        if (coupon == null ) {
            throw new BadRequestException("优惠券不存在");
        }
        if ( ObjectUtils.notEqual(coupon.getUserId(),userId)) {
            throw new BadRequestException("只允许核销自己的优惠券");
        }


        //更新优惠券表的记录，更新为已使用
        boolean update = lambdaUpdate()
                .le(Coupon::getAmountCondition, couponUseReqDTO.getTotalAmount())//满减金额小于等于订单金额
                .gt(Coupon::getValidityTime, LocalDateTime.now())//优惠券未过期
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())//优惠券状态未使用,才能继续下面更新状态为已使用
                .eq(Coupon::getUserId, userId)//用户只允许核销自己的优惠券
                .set(Coupon::getStatus, CouponStatusEnum.USED.getStatus())//修改为已使用
                .set(Coupon::getUseTime, LocalDateTime.now())//使用时间
                .set(Coupon::getOrdersId, couponUseReqDTO.getOrdersId())//订单id
                .update();
        if(!update){
            throw new CommonException("优惠券核销失败");
        }

        //向优惠券核销表写入记录
        CouponWriteOff couponWriteOff =new CouponWriteOff();
        couponWriteOff.setId(IdUtils.getSnowflakeNextId());  //雪花算法,按一定的规则,生成的id不会重复
        //优惠券id
        couponWriteOff.setCouponId(couponUseReqDTO.getId());
        //用户id
        couponWriteOff.setUserId(userId);
        //订单id
        couponWriteOff.setOrdersId(couponUseReqDTO.getOrdersId());
        //活动id
        couponWriteOff.setActivityId(coupon.getActivityId());
        //核销时间
        couponWriteOff.setWriteOffTime(LocalDateTime.now());
        //核销人的手机号和名称
        couponWriteOff.setWriteOffManPhone(coupon.getUserPhone());
        couponWriteOff.setWriteOffManName(coupon.getUserName());

        boolean save = couponWriteOffService.save(couponWriteOff);
        if(!save){
            throw new CommonException("优惠券核销失败");
        }

        //返回优惠金额(就是返回最终优惠卷减完的金额)
        BigDecimal bigDecimal = CouponUtils.calDiscountAmount(coupon, couponUseReqDTO.getTotalAmount());
        CouponUseResDTO couponUseResDTO = new CouponUseResDTO();
        couponUseResDTO.setDiscountAmount(bigDecimal);
        return couponUseResDTO;
    }


    /**
     * 抢卷
     * @param seizeCouponReqDTO
     */
    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        //校验活动的有效性
        //从redis查询活动信息---这里优化的就是redis,要是从数据库查,那就前功尽弃了
        ActivityInfoResDTO activityInfoResDTO = activityService.getActivityInfoByIdFromCache(seizeCouponReqDTO.getId());
        //如果活动为空，或过期了
        if(ObjectUtils.isNull(activityInfoResDTO) || activityInfoResDTO.getDistributeEndTime().isBefore(LocalDateTime.now())){
            throw new CommonException(SEIZE_COUPON_FAILD, "活动已结束");
        }
        //如果活动未开始
        if(activityInfoResDTO.getDistributeStartTime().isAfter(LocalDateTime.now())){
            throw new CommonException(SEIZE_COUPON_FAILD, "活动未开始");
        }


        //准备调用 lua脚本的数据
        //活动id
        long activityId = seizeCouponReqDTO.getId();
        //当前登录用户id
        long userId = UserContext.currentUserId();

        int n = 10;//队列总数
        //准备调用lua脚本的keys和args

        //序号：就是redisKey后大括号中的内容，多个redisKey的内容必须一致，就是活动id % 10
        int index = (int) (activityId % n);

        //同步队列key  完整的key：QUEUE:COUPON:SEIZE:SYNC:{活动id%10}
        String couponSeizeSyncRedisKey  = RedisSyncQueueUtils.getQueueRedisKey(COUPON_SEIZE_SYNC_QUEUE_NAME, index);
        //库存key
        String resourceStockRedisKey  = String.format(COUPON_RESOURCE_STOCK, index);
        //抢券成功列表key
        String couponSeizeListRedisKey  = String.format(COUPON_SEIZE_LIST, activityId, index);
        List<String> keys = Arrays.asList(couponSeizeSyncRedisKey, resourceStockRedisKey, couponSeizeListRedisKey);

        //调用抢券的lua脚本
        Object execute = redisTemplate.execute(seizeCouponScript, keys, activityId, userId);
        log.info("执行lua脚本的结果:{}",execute);
        
        if(ObjectUtils.isNull(execute)){
            throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
        }

        //lua返回的结果(转数字)
        long result = NumberUtils.parseLong(execute.toString());
        if(result>0){
            //抢券成功
            return ;
        }
        if(result==-1){
            throw new CommonException(SEIZE_COUPON_FAILD, "限领一张");
        }
        if(result==-2||result==-4){
            throw new CommonException(SEIZE_COUPON_FAILD, "已抢光");
        }
        throw new CommonException(SEIZE_COUPON_FAILD, "抢券失败");
    }

    @Override
    public PageResult<CouponInfoResDTO> queryForPageOfOperation(CouponOperationPageQueryReqDTO couponOperationPageQueryReqDTO) {
        // 1.数据校验
        if (ObjectUtils.isNull(couponOperationPageQueryReqDTO.getActivityId())) {
            throw new BadRequestException("请指定活动");
        }
        // 2.数据查询
        // 分页 排序
        Page<Coupon> couponQueryPage = PageUtils.parsePageQuery(couponOperationPageQueryReqDTO, Coupon.class);
        // 查询条件
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Coupon::getActivityId, couponOperationPageQueryReqDTO.getActivityId());
        // 查询数据
        Page<Coupon> couponPage = baseMapper.selectPage(couponQueryPage, lambdaQueryWrapper);

        // 3.数据转化，并返回
        return PageUtils.toPage(couponPage, CouponInfoResDTO.class);
    }

    @Override
    public List<CouponInfoResDTO> queryForList(Long lastId, Long userId, Integer status) {

        // 1.校验
        if (status > 3 || status < 1) {
            throw new BadRequestException("请求状态不存在");
        }
        // 2.查询准备
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper.eq(Coupon::getStatus, status)
                .eq(Coupon::getUserId, userId)
                .lt(ObjectUtils.isNotNull(lastId), Coupon::getId, lastId);
        // 查询字段
        lambdaQueryWrapper.select(Coupon::getId);
        // 排序
        lambdaQueryWrapper.orderByDesc(Coupon::getId);
        // 查询条数限制
        lambdaQueryWrapper.last(" limit 10 ");
        // 3.查询数据(数据中只含id)
        List<Coupon> couponsOnlyId = baseMapper.selectList(lambdaQueryWrapper);
        //判空
        if (CollUtils.isEmpty(couponsOnlyId)) {
            return new ArrayList<>();
        }

        // 4.获取数据且数据转换
        // 优惠id列表
        List<Long> ids = couponsOnlyId.stream()
                .map(Coupon::getId)
                .collect(Collectors.toList());
        // 获取优惠券数据
        List<Coupon> coupons = baseMapper.selectBatchIds(ids);
        // 数据转换
        return BeanUtils.copyToList(coupons, CouponInfoResDTO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long activityId) {
        lambdaUpdate()
                .set(Coupon::getStatus, CouponStatusEnum.VOIDED.getStatus())
                .eq(Coupon::getActivityId, activityId)
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .update();
    }

    @Override
    public Integer countReceiveNumByActivityId(Long activityId) {
        return lambdaQuery().eq(Coupon::getActivityId, activityId)
                .count();
    }

    @Override
    public void processExpireCoupon() {
        lambdaUpdate()
                .set(Coupon::getStatus, CouponStatusEnum.INVALID.getStatus())
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .le(Coupon::getValidityTime, DateUtils.now())
                .update();
    }
    
    
}
